#region License

/* Copyright (c) 2008, Dusty Candland
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * * Neither the name of Dusty Candland nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Xml;

namespace NS3Library.Amazon
{
    public static class AwsS3Parser
    {
        private const string _pathToContentsKey = "amz:Key";
        private const string _pathToContentsLastModified = "amz:LastModified";
        private const string _pathToContentsETag = "amz:ETag";
        private const string _pathToContentsSize = "amz:Size";
        private const string _pathToContentsOwner = "amz:Owner";
        private const string _pathToContentsOwnerID = "amz:ID";
        private const string _pathToContentsOwnerDisplayName = "amz:DisplayName";
        private const string _pathToBucket = "//amz:Buckets/amz:Bucket";
        private const string _pathToName = "./amz:Name";
        private const string _pathToListBucketName = "/amz:ListBucketResult/amz:Name";
        private const string _pathToListBucketPrefix = "/amz:ListBucketResult/amz:Prefix";
        private const string _pathToListBucketMarker = "/amz:ListBucketResult/amz:Marker";
        private const string _pathToListBucketMaxKeys = "/amz:ListBucketResult/amz:MaxKeys";
        private const string _pathToListBucketDelimiter = "/amz:ListBucketResult/amz:Delimiter";
        private const string _pathToListBucketIsTruncated = "/amz:ListBucketResult/amz:IsTruncated";
        private const string _errorElementName = "Error";
        private const string _pathToErrorCode = "/Error/Code";
        private const string _pathToErrorMessage = "/Error/Message";

        public static List<IS3Bucket> GetBucketList(XmlDocument doc)
        {
            if (doc == null)
                return null;

            var buckets = new List<IS3Bucket>();
            XmlNamespaceManager ns = GetNamespace(doc.NameTable);

            foreach (XmlNode node in doc.SelectNodes(_pathToBucket, ns))
                buckets.Add(new AwsBucket(node.SelectSingleNode(_pathToName, ns).InnerText));

            return buckets;
        }

        public static IS3Bucket GetBucket(XmlDocument doc)
        {
            if (doc == null)
                return null;

            XmlNamespaceManager ns = GetNamespace(doc.NameTable);

            string name = doc.SelectSingleNode(_pathToListBucketName, ns).InnerText;
            string prefix = doc.SelectSingleNode(_pathToListBucketPrefix, ns).InnerText;
            string marker = doc.SelectSingleNode(_pathToListBucketMarker, ns).InnerText;
            string maxKeysStr = doc.SelectSingleNode(_pathToListBucketMaxKeys, ns).InnerText;
            int maxKeys = 0;
            if (!string.IsNullOrEmpty(maxKeysStr))
                maxKeys = int.Parse(maxKeysStr, CultureInfo.InvariantCulture);
            XmlNode delimiterNode = doc.SelectSingleNode(_pathToListBucketDelimiter, ns);
            string delimiter = delimiterNode == null ? string.Empty : delimiterNode.InnerText;
            bool isTruncated = doc.SelectSingleNode(_pathToListBucketIsTruncated, ns).InnerText.Equals("true");

            IEnumerable<IS3ObjectKey> list = GetObjectKeys(doc.SelectNodes("/amz:ListBucketResult/amz:Contents", ns), ns);

            return new AwsBucket(name, prefix, maxKeys, marker, delimiter, isTruncated, list);
        }

        private static XmlNamespaceManager GetNamespace(XmlNameTable table)
        {
            var ns = new XmlNamespaceManager(table);
            ns.AddNamespace("amz", "http://s3.amazonaws.com/doc/2006-03-01/");
            return ns;
        }

        public static AwsStatus GetStatus(HttpStatusCode statusCode, NameValueCollection headers, XmlDocument doc)
        {
            string amzId2 = headers["x-amz-id-2"];
            string amzRequestId = headers["x-amz-request-id"];

            if (doc == null || !doc.DocumentElement.Name.Equals(_errorElementName))
                return new AwsStatus(amzId2, amzRequestId, statusCode);

            string code = doc.SelectSingleNode(_pathToErrorCode).InnerText;
            string message = doc.SelectSingleNode(_pathToErrorMessage).InnerText;
            string xml = doc.OuterXml;

            return new AwsStatus(amzId2, amzRequestId, statusCode, code, message, xml);
        }

        public static AwsObject GetDataObject(string bucketName, string key, AwsStatus awsStatus,
                                              NameValueCollection headers, Stream content)
        {
            // TODO: read until end, not content length
            int contentLength = int.Parse(headers[KnownHttpHeaderNames.ContentLength], CultureInfo.InvariantCulture);
            var bytes = new byte[contentLength];
            try
            {
                content.Read(bytes, 0, bytes.Length);
            }
            finally
            {
                content.Close();
            }

            DateTime lastModified = DateTime.Parse(headers[KnownHttpHeaderNames.LastModified]);
            string eTag = headers[KnownHttpHeaderNames.ETag];

            return new AwsObject(bucketName, key, lastModified, eTag, contentLength, null, IS3StorageClass.Standard,
                                 bytes,
                                 headers, awsStatus);
        }


        public static XmlDocument Xml(HttpWebResponse response)
        {
            if (response == null || response.ContentLength == 0)
                return null;

            if (!response.ContentType.Equals("application/xml"))
                return null;

            var xml = new XmlDocument();
            xml.Load(response.GetResponseStream());
            return xml;
        }

        public static IEnumerable<IS3ObjectKey> GetObjectKeys(XmlNodeList nodes, XmlNamespaceManager ns)
        {
            var list = new List<IS3ObjectKey>();
            foreach (XmlNode node in nodes)
            {
                string key = node.SelectSingleNode(_pathToContentsKey, ns).InnerText;
                DateTime lastModified = DateTime.Parse(node.SelectSingleNode(_pathToContentsLastModified, ns).InnerText);
                string eTag = node.SelectSingleNode(_pathToContentsETag, ns).InnerText;
                int size = int.Parse(node.SelectSingleNode(_pathToContentsSize, ns).InnerText);
                S3Owner owner = GetAwsOwner(node.SelectSingleNode(_pathToContentsOwner, ns), ns);

                var objectKey = new AwsObjectKey(key, lastModified, eTag, size, owner,
                                                 IS3StorageClass.Standard);

                list.Add(objectKey);
            }
            return list;
        }

        private static S3Owner GetAwsOwner(XmlNode node, XmlNamespaceManager ns)
        {
            if (node == null)
                return null;

            return new S3Owner(
                node.SelectSingleNode(_pathToContentsOwnerID, ns).InnerText,
                node.SelectSingleNode(_pathToContentsOwnerDisplayName, ns).InnerText
                );
        }
    }
}